﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MealBooking.Core.Services.Interface;
using System.IO;
using System.Xml;
using MealBooking.Core.Models.Accounts;
using System.Xml.Linq;
using System.Xml.Serialization;
using MealBooking.Core.Common;
using MealBooking.Core.Models.Orders;

namespace MealBooking.Core.Services.Impl
{
    public class AccountService : IAccountService
    {
        #region private
        static string FolderPath = Path.Combine(Utils.Utils.StorageLocation(), "Accounts");
        #endregion

        #region public static method
        public static List<Role> GetRoles(int roles)
        {
            Role role = (Role)roles;
            var accountRoles = new List<Role>();
            if ((role & Role.Admin) != 0)
            {
                accountRoles.Add(Role.Admin);
            }
            if ((role & Role.Customer) != 0)
            {
                accountRoles.Add(Role.Customer);
            }
            if ((role & Role.Manager) != 0)
            {
                accountRoles.Add(Role.Manager);
            }
            //if ((role & Role.User) != 0)
            //{
            accountRoles.Add(Role.User);
            //}
            return accountRoles;
        }
        #endregion

        #region Account
        #region private
        static string AccountFilePath = Path.Combine(FolderPath, "Accounts.xml");
        static object _accountLocker = new object();

        private static XDocument LoadAccountFile()
        {
            return Utils.Utils.InitXmlFile(AccountFilePath);
        }
        private void SaveAccountFile(XDocument doc)
        {
            Utils.Utils.SaveFile(_accountLocker, doc, AccountFilePath);
        }
        #endregion
        #region private method
        public Account UpdateAccount(Account account)
        {
            var doc = LoadAccountFile();
            var xmlAccount = doc.Root.Elements("Account").SingleOrDefault(a =>
                account.Id.Equals(a.Element("Id").Value));
            if (xmlAccount != null)
            {
                xmlAccount.Remove();
            }
            xmlAccount = ModelSerializer.Serialize(account);
            doc.Root.Add(xmlAccount);
            SaveAccountFile(doc);
            return AccountDetail(account);
        }
        #endregion
        public List<Account> AllAccounts()
        {
            var doc = LoadAccountFile();
            var xmlAccounts = doc.Root.Elements("Account");
            return ModelSerializer.Deserialize<Account>(xmlAccounts);
        }
        public Account AccountDetail(Account account)
        {
            var doc = LoadAccountFile();
            var xmlUser = doc.Root.Elements("Account").SingleOrDefault(a =>
                (account.Name == null ? false : account.Name.Equals(a.Element("Name").Value))
                || (account.Id == null ? false : account.Id.Equals(a.Element("Id").Value)));
            if (xmlUser == null)
            {
                return null;
            }
            Account user = ModelSerializer.Deserialize<Account>(xmlUser);
            user.Roles = AccountRoleDetail(user.Id);
            return user;
        }
        public Account ValidateUser(string userName, string password)
        {
            var account = AccountDetail(new Account { Name = userName });
            if (account == null)
            {
                return null;
            }
            if (!ValiteAccountPsd(account.Id, password))
            {
                return null;
            }
            return account;
        }
        public bool ChangePassword(string accountId, string oldPassword, string newPassword)
        {
            return ChangeAccountPsd(accountId, oldPassword, newPassword);
        }
        /// <summary>
        /// 修改用户组
        /// </summary>
        /// <param name="accountWithNewRoles"></param>
        /// <returns></returns>
        public bool ChangeRoles(string accountId, int newRoles)
        {
            return UpdateAccountRoles(accountId, newRoles);
        }
        /// <summary>
        /// 注册新用户
        /// </summary>
        /// <param name="newAccount"></param>
        /// <param name="errors">出错信息</param>
        /// <returns></returns>
        public Account RegisterNewAccount(Account newAccount)
        {
            if (newAccount == null || newAccount.Password == null)
            {
                return null;
            }
            newAccount.Id = Guid.NewGuid().ToString().ToLower();
            newAccount.IsActived = true;
            newAccount.RegisterTime = DateTime.Now;
            //save password:
            AddNewAccountPsd(newAccount.Id, newAccount.Password);
            //remove psd from account:
            newAccount.Password = null;
            //save Role:
            int roles = (int)(Role.Customer | Role.User);
            UpdateAccountRoles(newAccount.Id, roles);
            //remove Role from account:
            newAccount.Roles = null;
            //save customer:
            var newCustomer = new Customer
            {
                Id = newAccount.Id,
                Name = newAccount.Name,
            };
            UpdateCustomer(newCustomer);
            return UpdateAccount(newAccount);
        }
        #endregion

        #region Account Psd

        #region private
        static string AccountPsdFilePath = Path.Combine(FolderPath, "AccountPsds.xml");
        static object _accountPsdLocker = new object();

        private static XDocument LoadAccountPsdFile()
        {
            return Utils.Utils.InitXmlFile(AccountPsdFilePath);
        }
        private static void SaveAccountPsdFile(XDocument doc)
        {
            Utils.Utils.SaveFile(_accountPsdLocker, doc, AccountPsdFilePath);
        }
        #endregion

        #region private method
        private AccountPsd AccountPsd(string accountId)
        {
            var doc = LoadAccountPsdFile();
            var xmlAccountPsd = doc.Root.Elements("AccountPsd").SingleOrDefault(p =>
                accountId.Equals(p.Element("Id").Value));
            return ModelSerializer.Deserialize<AccountPsd>(xmlAccountPsd);
        }
        private bool ValiteAccountPsd(string accountId, string password)
        {
            var accountPsd = AccountPsd(accountId);
            if (accountPsd == null)
            {
                return false;
            }
            if (!password.Equals(accountPsd.Password))
            {
                return false;
            }
            return true;
        }
        private bool ChangeAccountPsd(string accountId, string oldPassword, string newPassword)
        {
            if (accountId == null || oldPassword == null || newPassword == null)
            {
                return false;
            }
            var doc = LoadAccountPsdFile();
            var xmlAccountPsd = doc.Root.Elements("AccountPsd").SingleOrDefault(p =>
                accountId.Equals(p.Element("Id").Value) && oldPassword.Equals(p.Element("Password").Value));
            if (xmlAccountPsd == null)
            {
                return false;
            }
            xmlAccountPsd.SetElementValue("Password", newPassword);
            SaveAccountPsdFile(doc);
            return true;
        }
        private void AddNewAccountPsd(string newAccountId, string newPassword)
        {
            var doc = LoadAccountPsdFile();
            var xmlPsd = doc.Root.Elements("AccountPsd").SingleOrDefault(p =>
               newAccountId.Equals(p.Element("Id").Value));
            if (xmlPsd != null)
            {
                xmlPsd.Remove();
            }
            var newPsd = new AccountPsd { Id = newAccountId, Password = newPassword };
            xmlPsd = ModelSerializer.Serialize(newPsd);
            doc.Root.Add(xmlPsd);
            SaveAccountPsdFile(doc);
        }
        #endregion

        #endregion

        #region Account Role

        #region private
        static string RoleFilePath = Path.Combine(FolderPath, "Roles/Roles.xml");
        static string AccountRolesFilePath = Path.Combine(FolderPath, "Roles/AccountRoles.xml");

        static object _accountRoleLocker = new object();
        static object _roleLocker = new object();

        private XDocument LoadRoleFile()
        {
            return Utils.Utils.InitXmlFile(RoleFilePath);
        }
        private XDocument LoadAccountRoleFile()
        {
            return Utils.Utils.InitXmlFile(AccountRolesFilePath);
        }
        private void SaveRoleFile(XDocument doc)
        {
            Utils.Utils.SaveFile(_roleLocker, doc, RoleFilePath);
        }
        private void SaveAccountRoleFile(XDocument doc)
        {
            Utils.Utils.SaveFile(_accountRoleLocker, doc, AccountRolesFilePath);
        }
        #endregion

        #region private method
        private AccountRole AccountRoleDetail(string accountId)
        {
            var doc = LoadAccountRoleFile();
            var xmlAccountRole = doc.Root.Elements("AccountRole").SingleOrDefault(r =>
                accountId.Equals(r.Element("Id").Value));
            return ModelSerializer.Deserialize<AccountRole>(xmlAccountRole);
        }
        private bool UpdateAccountRoles(string accountId, int newRoles)
        {
            var doc = LoadAccountRoleFile();
            var xmlAccountRoles = doc.Root.Elements("AccountRole").SingleOrDefault(r =>
            accountId.Equals(r.Element("Id").Value));
            if (xmlAccountRoles != null)
            {
                xmlAccountRoles.Remove();
            }
            var newAccountRoles = new AccountRole { Id = accountId, Roles = newRoles };
            xmlAccountRoles = ModelSerializer.Serialize(newAccountRoles);
            doc.Root.Add(xmlAccountRoles);
            SaveAccountRoleFile(doc);
            return true;
        }
        #endregion

        #endregion

        #region Customer
        #region private
        static string CustomerFilePath = Path.Combine(FolderPath, "Customers.xml");
        static object _customerRoleLocker = new object();
        #endregion
        #region private method
        private XDocument LoadCustomerFile()
        {
            return Utils.Utils.InitXmlFile(CustomerFilePath);
        }
        private void SaveCustomerFile(XDocument doc)
        {
            Utils.Utils.SaveFile(_customerRoleLocker, doc, CustomerFilePath);
        }
        #endregion
        public Customer UpdateCustomer(Customer newCustomer)
        {
            var doc = LoadCustomerFile();
            var xmlCustomer = doc.Root.Elements("Customer").SingleOrDefault(c =>
                newCustomer.Id.Equals(c.Element("Id").Value));
            if (xmlCustomer != null)
            {
                xmlCustomer.Remove();
            }
            xmlCustomer = ModelSerializer.Serialize(newCustomer);
            doc.Root.Add(xmlCustomer);
            SaveCustomerFile(doc);
            return CustomerDetail(newCustomer.Id);
        }
        public Customer CustomerDetail(string accountId)
        {
            XDocument doc = LoadCustomerFile();
            var xmlCustomer = doc.Root.Elements("Customer").SingleOrDefault(c =>
                accountId.Equals(c.Element("Id").Value));
            return ModelSerializer.Deserialize<Customer>(xmlCustomer);
        }
        public void PurchaseBookedOrders(List<Order> orders)
        {
            var doc = LoadCustomerFile();
            var operations = new List<BalanceHistory>();
            var xmlCustomers = doc.Root.Elements("Customer");
            foreach (var order in orders)
            {
                if (order != null && order.IsBooked)
                {
                    var xmlCustomer = xmlCustomers.SingleOrDefault(c =>
                        c.Element("Id").Value.Equals(order.Account.Id));
                    if (xmlCustomer != null)
                    {
                        double balance = (double)xmlCustomer.Element("Balance");
                        var operation = new BalanceHistory
                        {
                            Id = Guid.NewGuid().ToString().ToLower(),
                            OperationType = BalanceOperationType.BookedOrder,
                            Account = new Account { Id = order.Account.Id, Name = order.Account.Name },
                            OperatedBy = new Account { Id = order.Manager.Id, Name = order.Manager.Name },
                            OldBalance = balance,
                            Amount = order.TotalPrice,
                        };
                        balance -= order.TotalPrice;
                        xmlCustomer.SetElementValue("Balance", balance);
                        operation.NewBalance = balance;
                        operation.OperatedTime = DateTime.Now;
                        operations.Add(operation);
                    }
                }
            }
            SaveCustomerFile(doc);
            AddBalanceOperations(operations);
        }
        public void AddBalance(BalanceHistory balanceHistory)
        {
            var doc = LoadCustomerFile();
            var xmlCustomer = doc.Root.Elements("Customer").SingleOrDefault(c =>
                balanceHistory.Account.Id.Equals(c.Element("Id").Value));
            var oldBalance = (double)xmlCustomer.Element("Balance");
            balanceHistory.Id = Guid.NewGuid().ToString().ToLower();
            balanceHistory.OldBalance = oldBalance;
            balanceHistory.OperatedTime = DateTime.Now;
            var newBalance = oldBalance + balanceHistory.Amount;
            xmlCustomer.Element("Balance").SetValue(newBalance);
            SaveCustomerFile(doc);
            balanceHistory.NewBalance = newBalance;
            AddBalanceOperations(new List<BalanceHistory> { balanceHistory });
        }
        #endregion

        #region balance

        #region private
        static string BalanceHistoryFilePath = Path.Combine(FolderPath, "BalanceHistories", "BalanceHistories.xml");
        static object _BalanceHistoryLocker = new object();
        private void SaveBalanceHistoryFile(XDocument doc)
        {
            Utils.Utils.SaveFile(_BalanceHistoryLocker, doc, BalanceHistoryFilePath);
        }
        private XDocument LoadBalanceHistoryFile()
        {
            return Utils.Utils.InitXmlFile(BalanceHistoryFilePath);
        }

        #endregion
        private void AddBalanceOperations(List<BalanceHistory> operations)
        {
            if (operations != null && operations.Count != 0)
            {
                var doc = LoadBalanceHistoryFile();
                foreach (var operation in operations)
                {
                    var xmlOperation = ModelSerializer.Serialize(operation);
                    doc.Root.Add(xmlOperation);
                }
                SaveBalanceHistoryFile(doc);
            }
        }
        public List<BalanceHistory> BalanceHistory(string accountId)
        {
            var doc = LoadBalanceHistoryFile();
            var xmlBalanceHistories = doc.Root.Elements("BalanceHistory");
            var userBalanceHistories = xmlBalanceHistories.Where(b =>
                accountId.Equals(b.Element("Account").Element("Id").Value));
            return ModelSerializer.Deserialize<BalanceHistory>(userBalanceHistories);
        }
        #endregion

        #region Manager
        #region private
        static string ManagerFilePath = Path.Combine(FolderPath, "Managers.xml");
        static object _managerLocker = new object();
        #endregion
        #region private method
        private XDocument LoadManagerFile()
        {
            return Utils.Utils.InitXmlFile(ManagerFilePath);
        }
        private void SaveManagerFile(XDocument doc)
        {
            Utils.Utils.SaveFile(_managerLocker, doc, ManagerFilePath);
        }
        #endregion
        public List<Manager> AllManagers()
        {
            var doc = LoadManagerFile();
            var xmlManagers = doc.Root.Elements("Manager");
            return ModelSerializer.Deserialize<Manager>(xmlManagers);
        }
        public Manager ManagerDetail(string accountId)
        {
            var doc = LoadManagerFile();
            var xmlManager = doc.Root.Elements("Manager").SingleOrDefault(m =>
                accountId.Equals(m.Element("Id").Value));
            return ModelSerializer.Deserialize<Manager>(xmlManager);
        }
        #endregion
    }
}
